Skip to content

Conversation

@BP602
Copy link
Owner

@BP602 BP602 commented Sep 21, 2025

Summary

  • expand the shared 7TV socket logging to cover chatroom registration, subscription checks, and per-event routing details
  • add renderer-side logs that trace cosmetic catalog updates and entitlement processing back to the originating chatroom and username
  • surface cosmetics store updates with badge/paint counts and entitlement identifiers to simplify debugging of cached styles

Testing

  • npm run lint

https://chatgpt.com/codex/tasks/task_e_68cfcfb0a35c833190e666f945a5bba7

Summary by CodeRabbit

  • New Features
    • Renderer can now fetch channel cosmetics (badges/paints); initial cosmetics load attempts when opening channels.
  • Improvements
    • Switched to a shared 7TV connection model and strengthened handling of cosmetics and entitlement events across chatrooms.
    • More defensive handling for user styles and entitlement changes to reduce missed or duplicated state.
  • Chores
    • Added gated verbose telemetry for presence and 7TV lifecycle events and expanded diagnostic logging for debugging.

@coderabbitai
Copy link

coderabbitai bot commented Sep 21, 2025

Walkthrough

Replaced per-chatroom 7TV sockets with a shared SharedStvWebSocket path, added telemetry helpers and gated verbose logging, introduced channel cosmetics fetch and cosmetics store test hooks, expanded diagnostics across STV lifecycle and opcode handling, and changed SharedStvWebSocket.addChatroom signature. (39 words)

Changes

Cohort / File(s) Summary of Changes
Shared 7TV socket & routing
utils/services/seventv/sharedStvWebSocket.js
Added extensive diagnostics/telemetry for chatroom registration, subscription lifecycle, message parsing, event routing (cosmetic.create, entitlement.create/delete), entitlements handling, and per-message dispatch; extended addChatroom signature to accept optional chatroomData.
Per-chatroom usage -> shared path
src/renderer/src/providers/ChatProvider.jsx, utils/services/connectionManager.js
Disabled/logged-out per-chatroom connectToStvWebSocket usage in favor of shared ConnectionManager; updated add/sync flows to pass chatroom object to shared manager; added many guarded logs around shared-path event handling and presence updates.
STV WebSocket client telemetry/opcode handling
utils/services/seventv/stvWebsocket.js
Reworked opcode handling with explicit opcode switch, added telemetry for opcodes, connection errors/reconnect scheduling, and guarded dispatch processing to require msg.d.body; extended entitlement.create handling to include emote sets.
Cosmetics API + provider changes
utils/services/seventv/stvAPI.js, src/preload/index.js, src/renderer/src/providers/CosmeticsProvider.jsx
Added getChannelCosmetics(channelId) (+ argbToRgba helper) and exported it; exposed getChannelCosmetics via preload (app.stv.getChannelCosmetics); enhanced CosmeticsStore with verbose logging, removeUserStyle, testCosmetics, and global debug hooks (window.testCosmeticsStore, window.cosmeticsStore).
Telemetry helpers & gated debug logging
src/renderer/src/telemetry/webTracing.js, src/telemetry/user-analytics.js, src/main/index.js
Added getTelemetryDebug, getTelemetryLevel, and shouldLogDebug; exposed utils to window in renderer; replaced many unconditional console logs with shouldLogDebug() guards to gate verbose telemetry output.
Renderer components — cosmetics access changes
src/renderer/src/components/Messages/Message.jsx, src/renderer/src/components/Messages/ModActionMessage.jsx
Switched shallow import path; consolidated userStyle retrieval via store subscription in Message.jsx; in ModActionMessage.jsx replaced hook usage with direct useCosmeticsStore.getState().getUserStyle(...) calls (non-reactive direct access).
Connection manager & other instrumentation
utils/services/connectionManager.js, src/renderer/src/providers/*, utils/services/* (various)
Added handler registration logs, diagnostic ID variants for chatrooms, comments/notes about cosmetics loading, and many defensive logs/guards around presence, cosmetics, entitlements, and connection lifecycle.

Sequence Diagram(s)

sequenceDiagram
  participant UI as App / Chat UI
  participant Pre as Preload (app.stv)
  participant Shared as SharedStvWebSocket
  participant ChatProv as ChatProvider
  participant Store as CosmeticsStore

  rect rgba(230,245,230,0.25)
    Note over Pre,Shared: Channel cosmetics can be fetched (getChannelCosmetics) or arrive via shared WS events
  end

  UI->>Pre: app.stv.getChannelCosmetics(channelId)
  Pre-->>UI: cosmetics payload (badges, paints)
  UI->>ChatProv: open channel -> provide cosmetics (if returned)
  ChatProv->>Store: addCosmetics (guarded logs)

  UI->>Shared: register chatroom (addChatroom with chatroomData)
  Shared-->>Shared: subscribe per-chatroom events (telemetry)
  Shared->>Shared: receive message (cosmetic.create / entitlement.create / entitlement.delete)
  alt event maps to chatroom
    Shared->>ChatProv: route event (chatroomId)
    ChatProv->>Store: addUserStyle / removeUserStyle (guarded)
  else global/broadcast
    Shared->>Store: broadcast event (chatroomId = null)
    Store->>Store: update state & expose test hooks
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I hop through logs with twitching nose,
One socket shared where every channel goes.
Badges, paints, and tiny traces gleam,
I stash them in a store and test the dream.
A rabbit cheers—more telemetry, less prose 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title concisely and accurately describes the primary intent of the changeset—adding diagnostic/logging around the 7TV cosmetics flow—and is specific and short enough for teammates to understand the main change at a glance. It appropriately frames the work as a chore and need not enumerate ancillary updates like telemetry gating or API additions.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/find-7tv-cosmetics-fetch-and-storage-method

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (8)
src/renderer/src/providers/CosmeticsProvider.jsx (3)

15-22: Gate verbose logs behind a debug flag and use console.debug

Avoid always-on logging in the renderer; gate it with RENDERER_VITE_* and demote to debug.

Apply this diff:

-    console.log(
+    if (import.meta.env.DEV || import.meta.env.RENDERER_VITE_DEBUG_STV === 'true') console.debug(
       `[CosmeticsStore] Upserting style for ${transformedUsername}`,
       {
         badgeId: userStyle?.style?.badge_id,
         paintId: userStyle?.style?.paint_id,
         entitlementId: body?.object?.id,
       },
     );

66-72: Same: gate catalog update logs

Keep cosmetics catalog logs behind a flag to reduce noise in production.

-    console.log(
+    if (import.meta.env.DEV || import.meta.env.RENDERER_VITE_DEBUG_STV === 'true') console.debug(
       `[CosmeticsStore] Updating global cosmetics catalog`,
       {
         badges: body?.badges?.length,
         paints: body?.paints?.length,
       },
     );

12-13: Normalize usernames consistently (hyphen vs underscore)

You store with toLowerCase() only, but producer code (ChatProvider) replaces “-” → “_”. This can cause cache misses on getUserStyle.

-    const transformedUsername = username.toLowerCase();
+    const transformedUsername = username.replaceAll("-", "_").toLowerCase();
-    const transformedUsername = username.toLowerCase();
+    const transformedUsername = username.replaceAll("-", "_").toLowerCase();

Also applies to: 49-50

src/renderer/src/providers/ChatProvider.jsx (4)

1896-1904: Make 7TV “received event” logs debug‑gated

This fires for every WS event; keep it dev/debug only.

-    console.log(
+    if (import.meta.env.DEV || import.meta.env.RENDERER_VITE_DEBUG_STV === 'true') console.debug(
       `[ChatProvider] Received 7TV event ${type} for ${chatroomId ?? 'broadcast'}`,
       {
         badgeCount: body?.badges?.length,
         paintCount: body?.paints?.length,
         entitlementUser: body?.object?.user?.username,
       },
     );

1912-1918: Gate cosmetic catalog logs

Reduce renderer noise; keep under RENDERER_VITE_DEBUG_STV.

-        console.log(
+        if (import.meta.env.DEV || import.meta.env.RENDERER_VITE_DEBUG_STV === 'true') console.debug(
           `[ChatProvider] Applying cosmetic catalog update for ${chatroomId ?? 'all chatrooms'}`,
           {
             badges: body?.badges?.length,
             paints: body?.paints?.length,
           },
         );

1922-1924: Avoid duplicating username canonicalization here

CosmeticsProvider should own normalization; pass the raw Kick username and let the store normalize consistently.

-        const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
+        const transformedUsername = username; // normalization handled inside CosmeticsProvider

1924-1931: Gate entitlement processing logs

Same rationale: keep these as debug.

-        console.log(
+        if (import.meta.env.DEV || import.meta.env.RENDERER_VITE_DEBUG_STV === 'true') console.debug(
           `[ChatProvider] Processing entitlement for ${transformedUsername || 'unknown user'}`,
           {
             badgeId: body?.object?.user?.style?.badge_id,
             paintId: body?.object?.user?.style?.paint_id,
             chatroomId,
           },
         );
utils/services/seventv/sharedStvWebSocket.js (1)

177-179: Gate shared 7TV logs behind a single DEBUG flag; use console.debug

These logs are frequent. Add a module‑level DEBUG flag (VITE_* per guidelines for shared code) and switch to console.debug to avoid production noise.

-    console.log(
+    if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug(
       `[Shared7TV]: Registered chatroom ${chatroomId} (kick=${channelKickID}, stvUser=${stvId}, stvSet=${stvEmoteSetId})`,
     );
-    console.log(
+    if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug(
       `[Shared7TV]: Preparing subscriptions for chatroom ${chatroomId} (kick=${chatroomData.channelKickID}, emoteSet=${chatroomData.stvEmoteSetId})`,
     );
-      console.log(
+      if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug(
         `[Shared7TV]: Cosmetic subscription already active for Kick channel ${channelKickID} (chatroom ${chatroomId})`,
       );
-      console.log(
+      if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug(
         `[Shared7TV]: Entitlement subscription already active for Kick channel ${channelKickID} (chatroom ${chatroomId})`,
       );
-        console.log(
+        if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug(
           `[Shared7TV]: Received ${type} for ${chatroomId === null ? 'broadcast' : `chatroom ${chatroomId}`}`,
           {
             channelContext: body?.context || body?.condition || null,
             entitlementUser: body?.object?.user?.username,
           },
         );
-            console.log(
+            if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug(
               `[Shared7TV]: Forwarding cosmetic catalog update to ${chatroomId === null ? 'all chatrooms' : chatroomId}`,
               {
                 badgeCount: cosmetics?.badges?.length,
                 paintCount: cosmetics?.paints?.length,
               },
             );
-              console.log(
+              if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug(
                 `[Shared7TV]: Forwarding entitlement for ${body?.object?.user?.username || 'unknown user'}`,
                 {
                   chatroomId,
                   badgeId: body?.object?.user?.style?.badge_id,
                   paintId: body?.object?.user?.style?.paint_id,
                 },
               );
-      console.log(
+      if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug(
         `[Shared7TV]: Unable to directly map ${type} to a chatroom, broadcasting`,
         {
           contextId,
           knownChatrooms: Array.from(this.chatrooms.values()).map((data) => data.channelKickID),
         },
       );

Also applies to: 405-407, 485-487, 516-518, 592-599, 628-634, 649-656, 696-704

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4830af1 and 34168d2.

📒 Files selected for processing (3)
  • src/renderer/src/providers/ChatProvider.jsx (1 hunks)
  • src/renderer/src/providers/CosmeticsProvider.jsx (2 hunks)
  • utils/services/seventv/sharedStvWebSocket.js (9 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**

📄 CodeRabbit inference engine (AGENTS.md)

**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes

Files:

  • utils/services/seventv/sharedStvWebSocket.js
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/providers/ChatProvider.jsx
!dist/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not commit built installers/artifacts in dist/

Files:

  • utils/services/seventv/sharedStvWebSocket.js
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/renderer/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead

Files:

  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))

Files:

  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'

Files:

  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use shared non-secret variables via import.meta.env.VITE_*

Files:

  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/providers/ChatProvider.jsx
🧬 Code graph analysis (3)
utils/services/seventv/sharedStvWebSocket.js (1)
utils/services/seventv/stvWebsocket.js (5)
  • body (13-13)
  • cosmetics (3-6)
  • data (17-17)
  • data (37-37)
  • data (130-130)
src/renderer/src/providers/CosmeticsProvider.jsx (4)
src/renderer/src/components/Messages/Message.jsx (1)
  • userStyle (44-44)
utils/services/seventv/sharedStvWebSocket.js (1)
  • body (17-17)
src/main/index.js (1)
  • body (720-720)
utils/services/seventv/stvWebsocket.js (1)
  • body (13-13)
src/renderer/src/providers/ChatProvider.jsx (3)
utils/services/seventv/sharedStvWebSocket.js (1)
  • body (17-17)
utils/services/seventv/stvWebsocket.js (1)
  • body (13-13)
src/renderer/src/providers/CosmeticsProvider.jsx (1)
  • useCosmeticsStore (3-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test-builds (windows-latest)
🔇 Additional comments (1)
utils/services/seventv/sharedStvWebSocket.js (1)

136-153: Avoid require() in renderer-bundled code — use a renderer-safe tracer lookup

Found require('@opentelemetry/api') in utils/services/seventv/sharedStvWebSocket.js (lines 136–153). require() can break Vite/ESM renderer bundles — replace with a ChatProvider-style window lookup and fallback:

const getTracer = () => {
  try {
    const tracerApi = (typeof window !== 'undefined' && (window.__KT_TRACE_API__?.trace || window.__KT_TRACER__?.trace)) || null;
    return tracerApi?.getTracer?.('kicktalk-shared-7tv-websocket', '1.0.0');
  } catch {
    return null;
  }
};
const tracer = getTracer() || { startSpan: () => ({ end() {}, setStatus() {}, recordException() {}, addEvent() {}, setAttribute() {}, setAttributes() {} }) };

Confirm whether this file is bundled into the renderer; if it is, apply the replacement.

…unctions

Added detailed diagnostic logging throughout 7TV cosmetics flow and test functions
to identify where the fetch → receive → store → apply chain breaks.
@BP602
Copy link
Owner Author

BP602 commented Sep 21, 2025

🔍 7TV Cosmetics Diagnostic Findings

I've conducted a comprehensive investigation into why 7TV cosmetics (badges/paints) are not appearing for users. Through systematic diagnostic logging and testing, I've identified the root cause and confirmed the solution path.


🎯 Root Cause Identified

7TV cosmetics are not appearing because KickTalk is missing initial API calls to fetch existing cosmetics. The WebSocket system only provides real-time updates, not initial data.


📊 Diagnostic Evidence

✅ What's Working:

  • WebSocket Connections: Connected and receiving heartbeats
  • Subscriptions: Successfully subscribed to cosmetic.* and entitlement.* events
  • Event Processing: ChatProvider → CosmeticsStore → UI chain works perfectly
  • Store/Apply Logic: Confirmed via test function (window.testCosmeticsStore())

❌ What's Missing:

  • No dispatch events: Zero op: 0 cosmetic events received from 7TV
  • No initial fetching: No GraphQL calls to load existing cosmetics on connection
  • Missing user ID: No valid 7TV user ID for user-scoped subscriptions

🔬 Technical Analysis

WebSocket Flow Analysis:

[Shared7TV]: WebSocket connected ✅
[Shared7TV]: Subscribed to cosmetic.* events ✅  
[Shared7TV]: Subscribed to entitlement.* events ✅
[Shared7TV]: ACK received for all subscriptions ✅
[Shared7TV]: Only heartbeats received, no dispatch events ❌

Store/Apply Chain Test:

[CosmeticsStore TEST]: addCosmetics called ✅
[CosmeticsStore TEST]: addUserStyle called ✅  
[CosmeticsStore TEST]: getUserStyle result: {badge: {...}, paint: {...}} ✅

Real User Cosmetics:

[CosmeticsStore]: getUserStyle for [username] 
  → hasUserStyle: false, totalBadges: 0, totalPaints: 0 ❌

📚 Research Findings (via DeepWiki)

Consulted 7TV's official documentation and confirmed:

  1. WebSockets are for real-time updates only - Not initial data loading
  2. Initial cosmetics require GraphQL API calls:
    • Global catalog: getAllCosmetics query for all badges/paints
    • User cosmetics: UserInventory query for user's specific cosmetics
  3. Cosmetic events are user-scoped, not channel-scoped

🛠️ Solution Path

Based on the findings, here's what needs to be implemented:

1. Add Initial Cosmetics Fetching

// On connection, fetch global cosmetics catalog
const globalCosmetics = await fetch7TVCosmetics();
cosmeticsStore.addCosmetics(globalCosmetics);

// For each user, fetch their specific cosmetics  
const userCosmetics = await fetch7TVUserInventory(userId);
cosmeticsStore.addUserStyle(username, userCosmetics);

2. Fix User-Scoped Subscriptions

Currently subscribing to channel-scoped cosmetic events:

// Current (channel-scoped)
condition: { platform: "KICK", ctx: "channel", id: "972889" }

// Needed (user-scoped) 
condition: { object_id: stvUserId }

3. API Endpoints to Implement

  • GraphQL Endpoint: https://7tv.io/v4/gql
  • Global Cosmetics: getAllCosmetics query
  • User Inventory: UserInventory query

🧪 Testing Added

For future debugging, added:

  • Comprehensive diagnostic logging throughout the entire flow
  • Test function: window.testCosmeticsStore() to verify store/apply chain
  • Detailed event tracing for all 7TV WebSocket messages

💡 Next Steps

  1. Implement GraphQL API calls for initial cosmetics fetching
  2. Add user-scoped WebSocket subscriptions for real-time updates
  3. Ensure 7TV user ID detection is working properly
  4. Test with real cosmetic changes once initial fetching is implemented

✨ Impact

Once the initial API calls are implemented, users will see:

  • ✅ Existing cosmetics on connection
  • ✅ Real-time cosmetic updates via WebSocket
  • ✅ Proper badge and paint rendering in chat

The diagnostic framework is now in place to verify the fix works correctly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
src/renderer/src/providers/ChatProvider.jsx (3)

1514-1525: Gate high-volume 7TV event logs behind a debug switch.

These logs run for every WS event and can flood consoles; also they include user identifiers. Wrap with a debug guard like the presence logs.

-          console.log(`[ChatProvider] Received 7TV event from shared WebSocket`, {
+          if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Received 7TV event from shared WebSocket`, {
             type,
             chatroomId,
             hasBody: !!body,
             entitlementUser: body?.object?.user?.username,
             badgeId: body?.object?.user?.style?.badge_id,
             paintId: body?.object?.user?.style?.paint_id,
             badgeCount: body?.badges?.length,
             paintCount: body?.paints?.length
-          });
+          });
-            console.log(`[ChatProvider] Routing event ${type} to specific chatroom: ${chatroomId}`);
+            if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Routing event ${type} to specific chatroom: ${chatroomId}`);
-            console.log(`[ChatProvider] Broadcasting event ${type} to all chatrooms (${chatrooms.length} total)`);
+            if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Broadcasting event ${type} to all chatrooms (${chatrooms.length} total)`);

Also applies to: 1527-1527, 1531-1531


1913-1920: Gate diagnostic per-event log or make it debug-only.

Same rationale as above; large volume and includes identifiers.

-    console.log(
+    if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(
       `[ChatProvider] Received 7TV event ${type} for ${chatroomId ?? 'broadcast'}`,
       {
         badgeCount: body?.badges?.length,
         paintCount: body?.paints?.length,
         entitlementUser: body?.object?.user?.username,
       },
     );

4092-4094: Minor: align presence interval with comment or update comment.

The comment says “every 2 minutes” but the interval is 1 minute. Pick one.

-      1 * 60 * 1000,
+      2 * 60 * 1000,
utils/services/seventv/sharedStvWebSocket.js (4)

346-361: Gate subscription/setup logs or sample them.

These run on every (re)connect; make them conditional to avoid noise.

-      console.log(`[Shared7TV]: Starting subscription to all events for ${this.chatrooms.size} chatrooms`);
+      if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[Shared7TV]: Starting subscription to all events for ${this.chatrooms.size} chatrooms`);
...
-      console.log(`[Shared7TV]: Finished subscribing to all events`);
+      if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[Shared7TV]: Finished subscribing to all events`);
...
-      console.log(`[Shared7TV]: Setting up message handler`);
+      if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[Shared7TV]: Setting up message handler`);
...
-      console.log(`[Shared7TV]: Message handler setup complete`);
+      if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[Shared7TV]: Message handler setup complete`);

Also applies to: 363-366


619-630: Reduce per-message logging; add debug guard and/or sampling.

Logging raw payloads and every opcode will spam consoles and degrade perf.

-    this.chat.onmessage = (event) => {
-      console.log(`[Shared7TV]: Raw WebSocket message received:`, event.data);
+    this.chat.onmessage = (event) => {
+      const __dbg = !!window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.();
+      if (__dbg) console.log(`[Shared7TV]: Raw WebSocket message received`);
       try {
         const msg = JSON.parse(event.data);
-        console.log(`[Shared7TV]: Parsed message:`, {
+        if (__dbg) console.log(`[Shared7TV]: Parsed message:`, {
           op: msg?.op,
           type: msg?.d?.type,
           hasBody: !!msg?.d?.body,
-          fullMessage: msg
+          // Avoid dumping full message outside deep debug
+          // fullMessage: msg
         });

Optionally, sample with a counter (e.g., log every Nth heartbeat/dispatch).

Also applies to: 632-743


753-766: Consider redacting or gating full cosmetic/entitlement bodies.

Full bodies include user identifiers; prefer debug-only or field-level logs already present.


888-896: Map cosmetic/entitlement events to chatrooms when context.id matches.

You always broadcast; use context.id to route when possible.

-    if (type.startsWith("cosmetic.") || type.startsWith("entitlement.")) {
-      const contextId = body?.context?.id || body?.condition?.id || null;
-      console.log(
-        `[Shared7TV]: Unable to directly map ${type} to a chatroom, broadcasting`,
-        {
-          contextId,
-          knownChatrooms: Array.from(this.chatrooms.values()).map((data) => data.channelKickID),
-        },
-      );
-    }
+    if (type.startsWith("cosmetic.") || type.startsWith("entitlement.")) {
+      const contextId = body?.context?.id || body?.condition?.id;
+      if (contextId != null) {
+        for (const [id, data] of this.chatrooms) {
+          if (String(data.channelKickID) === String(contextId)) {
+            return id;
+          }
+        }
+      }
+      if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) {
+        console.log(`[Shared7TV]: Broadcasting ${type} (no chatroom match)`, {
+          contextId,
+          knownChatrooms: Array.from(this.chatrooms.values()).map((d) => d.channelKickID),
+        });
+      }
+    }
utils/services/seventv/stvWebsocket.js (1)

355-466: Add a debug guard for per-opcode logs.

Mirrors the shared WS suggestion; these fire frequently.

-        switch (msg?.op) {
+        const __dbg = !!window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.();
+        switch (msg?.op) {
           case 0:
-            console.log(`[7TV]: Dispatch event received for channel ${this.channelKickID}`, { type: msg?.d?.type });
+            if (__dbg) console.log(`[7TV]: Dispatch event received for channel ${this.channelKickID}`, { type: msg?.d?.type });
             try {
               window.app?.telemetry?.recordWebSocketMessage?.(`7tv_${this.channelKickID}`, 'dispatch', msg?.d?.type);
             } catch (_) {}
             break;
          ...
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34168d2 and b6dbb8a.

📒 Files selected for processing (4)
  • src/renderer/src/providers/ChatProvider.jsx (6 hunks)
  • src/renderer/src/providers/CosmeticsProvider.jsx (4 hunks)
  • utils/services/seventv/sharedStvWebSocket.js (15 hunks)
  • utils/services/seventv/stvWebsocket.js (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/src/providers/CosmeticsProvider.jsx
🧰 Additional context used
📓 Path-based instructions (6)
**

📄 CodeRabbit inference engine (AGENTS.md)

**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes

Files:

  • utils/services/seventv/stvWebsocket.js
  • utils/services/seventv/sharedStvWebSocket.js
  • src/renderer/src/providers/ChatProvider.jsx
!dist/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not commit built installers/artifacts in dist/

Files:

  • utils/services/seventv/stvWebsocket.js
  • utils/services/seventv/sharedStvWebSocket.js
  • src/renderer/src/providers/ChatProvider.jsx
src/renderer/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead

Files:

  • src/renderer/src/providers/ChatProvider.jsx
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))

Files:

  • src/renderer/src/providers/ChatProvider.jsx
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'

Files:

  • src/renderer/src/providers/ChatProvider.jsx
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use shared non-secret variables via import.meta.env.VITE_*

Files:

  • src/renderer/src/providers/ChatProvider.jsx
🧬 Code graph analysis (2)
utils/services/seventv/sharedStvWebSocket.js (1)
utils/services/seventv/stvWebsocket.js (5)
  • body (13-13)
  • cosmetics (3-6)
  • data (17-17)
  • data (37-37)
  • data (130-130)
src/renderer/src/providers/ChatProvider.jsx (3)
utils/services/seventv/sharedStvWebSocket.js (2)
  • body (17-17)
  • cosmetics (4-7)
utils/services/seventv/stvWebsocket.js (2)
  • body (13-13)
  • cosmetics (3-6)
src/renderer/src/providers/CosmeticsProvider.jsx (1)
  • useCosmeticsStore (3-217)
🪛 Biome (2.1.2)
src/renderer/src/providers/ChatProvider.jsx

[error] 1936-1937: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test-builds (windows-latest)
🔇 Additional comments (5)
src/renderer/src/providers/ChatProvider.jsx (2)

554-556: Nice: presence debug gated behind telemetry util.

Prevents noisy logs in production.


1643-1646: Good: individual 7TV connections explicitly disabled with clear log.

Avoids duplicate sockets and clarifies shared-connection intent.

Also applies to: 2204-2207

utils/services/seventv/sharedStvWebSocket.js (2)

177-180: Helpful registration log.

Clear, low-risk addition that aids troubleshooting.


511-515: Subscription diagnostics are clear and helpful.

Good visibility around idempotency and readyState checks.

Also applies to: 521-523, 535-539, 546-549, 555-556, 569-573

utils/services/seventv/stvWebsocket.js (1)

221-227: Telemetry on errors/retries looks good.

Provides useful context (attempt, delay, channel).

Also applies to: 233-237, 246-255

Comment on lines +1951 to +1964
`[ChatProvider] Processing entitlement for ${transformedUsername || 'unknown user'}`,
{
badgeId: body?.object?.user?.style?.badge_id,
paintId: body?.object?.user?.style?.paint_id,
chatroomId,
},
);
const addUserStyle = useCosmeticsStore?.getState()?.addUserStyle;
if (addUserStyle) {
console.log(`[ChatProvider] Calling CosmeticsStore.addUserStyle for ${transformedUsername}`);
addUserStyle(transformedUsername, body);
} else {
console.error(`[ChatProvider] CosmeticsStore.addUserStyle method not available!`);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Username normalization may diverge from store lookups.

You store under username.replaceAll('-', '_').toLowerCase() but CosmeticsStore.getUserStyle() normalizes to lowercase only. This can break lookups for hyphenated names.

Prefer a shared normalizer used by both writer and reader (e.g., keep original username from KICK connection and only lowercase), or update getUserStyle to mirror the same normalization.


🏁 Script executed:

#!/bin/bash
# Find all places replacing '-' with '_' for usernames to assess consistency
rg -nP "replaceAll\(\s*['\"]-['\"],\s*['\"]_['\"]\s*\).*username" -C2 --type=ts --type=tsx --type=js --type=jsx

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Searching for replaceAll occurrences (no type flags)..."
rg -n -C2 "replaceAll\(" || true

echo
echo "2) Searching for any replace( occurrences (showing context)..."
rg -n -C2 "replace\(" || true

echo
echo "3) Searching for transformedUsername occurrences..."
rg -n -C2 "transformedUsername" || true

echo
echo "4) Searching for getUserStyle occurrences..."
rg -n -C2 "getUserStyle" || true

echo
echo "5) Searching for addUserStyle occurrences..."
rg -n -C2 "addUserStyle" || true

echo
echo "6) Searching for CosmeticsStore occurrences..."
rg -n -C2 "CosmeticsStore" || true

echo
echo "7) Show ChatProvider.jsx snippet (lines 1900-1980) if file exists..."
if [ -f src/renderer/src/providers/ChatProvider.jsx ]; then
  sed -n '1900,1980p' src/renderer/src/providers/ChatProvider.jsx || true
else
  echo "src/renderer/src/providers/ChatProvider.jsx not found"
fi

Length of output: 47048


Inconsistent username normalization — hyphenated usernames will miss stored cosmetics

ChatProvider normalizes KICK usernames with username?.replaceAll("-", "_").toLowerCase() (src/renderer/src/providers/ChatProvider.jsx:1949) but CosmeticsProvider.addUserStyle/getUserStyle only call username.toLowerCase() (src/renderer/src/providers/CosmeticsProvider.jsx:32,75). Result: "foo-bar" vs "foo_bar" keys are inconsistent and lookups can fail.

Fix: centralize normalization (e.g., utils/normalizeUsername that replaces '-'→'_' and lowercases) and use it in both addUserStyle and getUserStyle — or have getUserStyle mirror ChatProvider's replaceAll behavior before lookup.

🤖 Prompt for AI Agents
In src/renderer/src/providers/ChatProvider.jsx around lines 1951-1964, username
normalization is inconsistent with CosmeticsProvider leading to hyphenated
usernames ("foo-bar") not matching stored keys ("foo_bar"); create a single
utils function (e.g., utils/normalizeUsername) that replaces '-' with '_' and
lowercases the string, then use that function in ChatProvider where usernames
are processed and in CosmeticsProvider's addUserStyle and getUserStyle (and any
lookup sites) so all keys are normalized identically before storage and lookup.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/renderer/src/providers/ChatProvider.jsx (3)

1514-1531: Gate verbose 7TV routing logs behind debug flag.

These logs fire for every event and chatroom; wrap them with shouldLogDebug to avoid hot‑path noise.

Apply:

-          console.log(`[ChatProvider] Received 7TV event from shared WebSocket`, {
+          if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Received 7TV event from shared WebSocket`, {
             type,
             chatroomId,
             hasBody: !!body,
             entitlementUser: body?.object?.user?.username,
             badgeId: body?.object?.user?.style?.badge_id,
             paintId: body?.object?.user?.style?.paint_id,
             badgeCount: body?.badges?.length,
             paintCount: body?.paints?.length
-          });
+          });
...
-            console.log(`[ChatProvider] Routing event ${type} to specific chatroom: ${chatroomId}`);
+            if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Routing event ${type} to specific chatroom: ${chatroomId}`);
...
-            console.log(`[ChatProvider] Broadcasting event ${type} to all chatrooms (${chatrooms.length} total)`);
+            if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Broadcasting event ${type} to all chatrooms (${chatrooms.length} total)`);

1913-1921: Gate per‑event 7TV logs.

Same rationale: avoid hot‑path logging unless debug is enabled.

Apply:

-    console.log(
+    if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(
       `[ChatProvider] Received 7TV event ${type} for ${chatroomId ?? 'broadcast'}`,
       {
         badgeCount: body?.badges?.length,
         paintCount: body?.paints?.length,
         entitlementUser: body?.object?.user?.username,
       },
     );

4087-4102: Presence interval comment mismatches code.

Comment says “every 2 minutes,” code runs every 1 minute. Align one of them.

Apply one (doc-only) fix:

-    // Send presence updates every 2 minutes
+    // Send presence updates every 1 minute

Or change interval to 2 * 60 * 1000.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6dbb8a and 4d923b5.

📒 Files selected for processing (1)
  • src/renderer/src/providers/ChatProvider.jsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**

📄 CodeRabbit inference engine (AGENTS.md)

**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes

Files:

  • src/renderer/src/providers/ChatProvider.jsx
src/renderer/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead

Files:

  • src/renderer/src/providers/ChatProvider.jsx
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))

Files:

  • src/renderer/src/providers/ChatProvider.jsx
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'

Files:

  • src/renderer/src/providers/ChatProvider.jsx
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use shared non-secret variables via import.meta.env.VITE_*

Files:

  • src/renderer/src/providers/ChatProvider.jsx
!dist/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not commit built installers/artifacts in dist/

Files:

  • src/renderer/src/providers/ChatProvider.jsx
🧬 Code graph analysis (1)
src/renderer/src/providers/ChatProvider.jsx (3)
utils/services/seventv/stvWebsocket.js (1)
  • body (13-13)
utils/services/seventv/sharedStvWebSocket.js (1)
  • body (17-17)
src/renderer/src/providers/CosmeticsProvider.jsx (1)
  • useCosmeticsStore (3-217)
🪛 Biome (2.1.2)
src/renderer/src/providers/ChatProvider.jsx

[error] 1929-1930: Duplicate case label.

The first similar label is here:

(lint/suspicious/noDuplicateCase)


[error] 1950-1950: This code will never be reached ...

... because this statement will break the flow of the code beforehand

(lint/correctness/noUnreachable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test-builds (windows-latest)
🔇 Additional comments (4)
src/renderer/src/providers/ChatProvider.jsx (4)

554-556: Good: noisy presence logs are now debug‑gated.

Reduces console spam and perf overhead in normal runs.


1643-1646: ACK: per‑chatroom 7TV sockets disabled.

Consistent with the new shared connection path.


4095-4097: Good: presence loop logging is debug‑gated.

Keeps the interval loop quiet in production.


1953-1967: Unify username normalization with CosmeticsStore to avoid missed lookups.

CosmeticsProvider lowers case only; replacing '-'→'_' here writes a different key and breaks getUserStyle. Pass lowercase only (or centralize a shared normalizer).

Apply:

-        const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
-        const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
-        console.log(
-          `[ChatProvider] Processing entitlement for ${transformedUsername || 'unknown user'}`,
+        const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
+        const normalizedUsername = username?.toLowerCase();
+        if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(
+          `[ChatProvider] Processing entitlement for ${normalizedUsername || 'unknown user'}`,
           {
             badgeId: body?.object?.user?.style?.badge_id,
             paintId: body?.object?.user?.style?.paint_id,
             chatroomId,
           },
         );
         const addUserStyle = useCosmeticsStore?.getState()?.addUserStyle;
         if (addUserStyle) {
-          console.log(`[ChatProvider] Calling CosmeticsStore.addUserStyle for ${transformedUsername}`);
-          addUserStyle(transformedUsername, body);
+          if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Calling CosmeticsStore.addUserStyle for ${normalizedUsername}`);
+          addUserStyle(normalizedUsername, body);
         } else {
           console.error(`[ChatProvider] CosmeticsStore.addUserStyle method not available!`);
         }

Optionally, extract a shared normalizeUsername helper and use it in both ChatProvider and CosmeticsProvider.

- Introduce getTelemetryLevel/getTelemetryDebug that prefer VITE_TELEMETRY_* and fall back to MAIN_/RENDERER_ variants for cross-process consistency
- Add shouldLogDebug and gate verbose logs on debug=true or level=VERBOSE
- Add guarded request/response/result logs to the main OTEL IPC relay for trace exports
- Expose telemetry utils (level/debug/shouldLogDebug) to window in the renderer
- Defaults remain NORMAL/false to avoid noise and simplify troubleshooting
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/telemetry/user-analytics.js (1)

548-639: Cleanup leaks: “historical” and “adoption” pruning never triggers (missing user_id/session_end_time/timestamp).

  • userBehaviorData entries written in endSession don’t include user_id, timestamp, or session_end_time, but cleanup relies on them.
  • featureAdoptionData purge checks data.user_id and data.session_end_time that don’t exist.

Result: unbounded growth over time.

Apply these changes:

  1. Persist required fields at session end:
   endSession(sessionId) {
@@
-    // Store session data for correlation analysis
-    userBehaviorData.set(sessionId, {
-      duration,
-      actions: session.actions.length,
-      engagement_rate: session.getEngagementRate(),
-      satisfaction_score: finalScore,
-      features_used: Array.from(session.featureUsage.keys()),
-      error_count: session.errorCount
-    });
+    // Store session data for correlation analysis and cleanup keys
+    userBehaviorData.set(sessionId, {
+      user_id: session.userId,
+      session_start_time: session.startTime,
+      session_end_time: Date.now(),
+      duration,
+      actions: session.actions.length,
+      engagement_rate: session.getEngagementRate(),
+      satisfaction_score: finalScore,
+      features_used: Array.from(session.featureUsage.keys()),
+      error_count: session.errorCount,
+    });
  1. Make cleanup predicates robust:
-    // Clean up historical behavior data (keep for 7x longer)
+    // Clean up historical behavior data (keep for 7x longer)
     const historicalCutoff = Date.now() - (maxAgeMs * 7);
     for (const [sessionId, data] of userBehaviorData) {
-      try {
-        // Extract timestamp from session ID format: session_timestamp_random
-        const sessionTimestamp = parseInt(sessionId.split('_')[1]) || 0;
-        if (sessionTimestamp < historicalCutoff) {
-          userBehaviorData.delete(sessionId);
-          cleanedHistoricalData++;
-        }
-      } catch (error) {
-        // If session ID format is unexpected, clean it up anyway if too old
-        if (data.timestamp && data.timestamp < historicalCutoff) {
-          userBehaviorData.delete(sessionId);
-          cleanedHistoricalData++;
-        }
-      }
+      const endedAt = Number(data?.session_end_time) || 0;
+      const startedAt = Number(data?.session_start_time) || 0;
+      const refTs = endedAt || startedAt;
+      if (refTs && refTs < historicalCutoff) {
+        userBehaviorData.delete(sessionId);
+        cleanedHistoricalData++;
+      }
     }
@@
-    for (const [userId, features] of featureAdoptionData) {
-      // Remove adoption data if no recent sessions for this user
-      const hasRecentSession = Array.from(userBehaviorData.values())
-        .some(data => data.user_id === userId && 
-                     data.session_end_time > adoptionCutoff);
+    for (const [userId, features] of featureAdoptionData) {
+      // Remove adoption data if no recent sessions for this user
+      const hasRecentSession = Array.from(userBehaviorData.values())
+        .some(data => data?.user_id === userId && Number(data?.session_end_time) > adoptionCutoff);

Optionally, initialize session_start_time at UserSession construction to make it explicit:

   constructor(sessionId, userId = null) {
@@
-    this.startTime = Date.now();
+    this.startTime = Date.now(); // alias: session_start_time
src/renderer/src/telemetry/webTracing.js (1)

379-393: Do not import @opentelemetry/resources in the renderer (breaks Vite ESM, contradicts file header).

Even dynamic import can drag the module into the bundle and trigger version/shape conflicts. You already inject resource attributes in the exported OTLP JSON; drop this import entirely.

-      try {
-        const resMod = await import('@opentelemetry/resources');
-        const ResourceCtor = resMod?.Resource || resMod?.default?.Resource;
-        if (ResourceCtor) {
-          const extra = new ResourceCtor({
-            'service.name': 'kicktalk-renderer',
-            'service.namespace': 'kicktalk',
-            'deployment.environment': deploymentEnv || 'development'
-          });
-          if (provider.resource && typeof provider.resource.merge === 'function') {
-            provider.resource = provider.resource.merge(extra);
-          }
-        }
-      } catch (e) {
-        try { console.debug('[Renderer OTEL]: Resource injection skipped:', e?.message || e); } catch {}
-      }
+      // Skip @opentelemetry/resources in renderer to avoid Vite ESM conflicts.
+      // Resource attributes are added in the OTLP JSON builder below.
🧹 Nitpick comments (7)
utils/services/connectionManager.js (1)

121-136: Gate Stv handler registration logs behind telemetry debug; downgrade missing-handler warning.

These logs will fire for every chatroom and can get noisy. Use shouldLogDebug and prefer console.debug for the “missing handler” case.

Apply this diff and add the helper shown below:

-      console.log(`[ConnectionManager] Registering onStvMessage handler`);
+      if (shouldLogDebug()) console.log(`[ConnectionManager] Registering onStvMessage handler`);
       this.stvWebSocket.addEventListener("message", handlers.onStvMessage);
-    } else {
-      console.warn(`[ConnectionManager] No onStvMessage handler provided!`);
+    } else {
+      if (shouldLogDebug()) console.debug(`[ConnectionManager] No onStvMessage handler provided`);
     }
@@
-      console.log(`[ConnectionManager] Registering onStvOpen handler`);
+      if (shouldLogDebug()) console.log(`[ConnectionManager] Registering onStvOpen handler`);
@@
-      console.log(`[ConnectionManager] Registering onStvConnection handler`);
+      if (shouldLogDebug()) console.log(`[ConnectionManager] Registering onStvConnection handler`);

Add near the top of the module:

// At top-level (once)
const shouldLogDebug = globalThis?.window?.__KT_TELEMETRY_UTILS__?.shouldLogDebug || (() => false);
src/renderer/src/telemetry/webTracing.js (5)

198-224: Renderer debug helpers OK; apply them consistently to top-level logs.

There are many unconditional console.logs earlier (e.g., Lines 32–35, 41, 49–50, 87–88, 120, 176–183, 189–196). Gate them with shouldLogDebug() for parity.

Example:

-console.log('[DEBUG] webTracing.js module loading - telemetry enabled:', telemetryEnabled);
+if (shouldLogDebug()) console.log('[DEBUG] webTracing.js module loading - telemetry enabled:', telemetryEnabled);

676-689: Exporter debug logs gated correctly. Consider redacting or truncating identifiers.

Not critical, but you can slice traceIds/spanIds to first 8–16 chars for compact logs.

Also applies to: 693-702, 709-720


1083-1111: IPCSimpleSpanProcessor still logs noisily; gate with shouldLogDebug().

These logs will emit for every span and can flood consoles.

-          console.log('[Renderer OTEL] Span started:', {
+          if (shouldLogDebug()) console.log('[Renderer OTEL] Span started:', {
@@
-            console.log('[Renderer OTEL] Span ended → exporting via IPC:', {
+            if (shouldLogDebug()) console.log('[Renderer OTEL] Span ended → exporting via IPC:', {
@@
-              console.log('[Renderer OTEL] Span export callback:', {
+              if (shouldLogDebug()) console.log('[Renderer OTEL] Span export callback:', {

Also applies to: 1113-1121, 1127-1135


1414-1601: Dev helpers (emitRendererTestSpan, compareOTLPPayloads) should be dev-only or gated.*

Expose these only in DEV or when shouldLogDebug() to avoid accidental use in production.

-// Expose helper to emit a manual renderer test span from DevTools
-if (true) {
+// Expose helpers only in dev or verbose debug
+if (import.meta.env.DEV || shouldLogDebug()) {
   // ... keep helper definitions ...
 }

2054-2055: Provider name mismatch breaks forceFlush in verifyGrafanaTraces.

You set window.KT_OTEL_PROVIDER earlier; here you read KT_TRACE_PROVIDER.

-        const provider = window.__KT_TRACE_PROVIDER__;
+        const provider = window.__KT_OTEL_PROVIDER__;
src/main/index.js (1)

715-719: OTLP IPC relay logs gated properly; add payload size guard to protect main thread.

Large payloads can stall the event loop. Reject overly large bodies early.

     const body = Buffer.from(JSON.stringify(exportJson), "utf8");
+    // Guardrail: reject payloads >5MB
+    const MAX_BYTES = 5 * 1024 * 1024;
+    if (body.length > MAX_BYTES) {
+      console.warn(`[OTEL IPC Relay][${requestId}] Rejecting oversized payload: ${body.length} bytes`);
+      return { ok: false, reason: "payload_too_large", requestId };
+    }

Also applies to: 764-767, 776-779, 799-801

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d923b5 and a63ec74.

📒 Files selected for processing (4)
  • src/main/index.js (5 hunks)
  • src/renderer/src/telemetry/webTracing.js (5 hunks)
  • src/telemetry/user-analytics.js (8 hunks)
  • utils/services/connectionManager.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**

📄 CodeRabbit inference engine (AGENTS.md)

**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes

Files:

  • src/renderer/src/telemetry/webTracing.js
  • src/telemetry/user-analytics.js
  • src/main/index.js
  • utils/services/connectionManager.js
src/renderer/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead

Files:

  • src/renderer/src/telemetry/webTracing.js
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))

Files:

  • src/renderer/src/telemetry/webTracing.js
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'

Files:

  • src/renderer/src/telemetry/webTracing.js
  • src/main/index.js
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use shared non-secret variables via import.meta.env.VITE_*

Files:

  • src/renderer/src/telemetry/webTracing.js
  • src/main/index.js
src/{telemetry,renderer/src/telemetry}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/{telemetry,renderer/src/telemetry}/**/*.{ts,tsx,js,jsx}: Place OpenTelemetry instrumentation under src/telemetry/** and src/renderer/src/telemetry/**
Configure OTLP HTTP export to Grafana Cloud; read endpoints/keys from env with main-safe prefixes; do not expose secrets to renderer

Files:

  • src/renderer/src/telemetry/webTracing.js
  • src/telemetry/user-analytics.js
!dist/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not commit built installers/artifacts in dist/

Files:

  • src/renderer/src/telemetry/webTracing.js
  • src/telemetry/user-analytics.js
  • src/main/index.js
  • utils/services/connectionManager.js
src/main/**/*.{ts,js,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

src/main/**/*.{ts,js,mts,mjs,cjs}: From the main process, load the built preload bundle (e.g., join(dirname, '../preload/index.js') or fileURLToPath(new URL('../preload/index.js', import.meta.url)) in ESM)
Keep BrowserWindow webPreferences.contextIsolation: true
Validate and sanitize all inputs received from renderer in main process handlers
Use ipcMain.handle('channel', fn) for request/response handlers in main
Access secrets in main via process.env or MAIN_VITE
*; never expose them to renderer
Use import.meta.env.MAIN_VITE
* for main-scoped non-secret config

Files:

  • src/main/index.js
🧠 Learnings (2)
📚 Learning: 2025-09-18T22:47:22.731Z
Learnt from: CR
PR: BP602/KickTalk#0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T22:47:22.731Z
Learning: Applies to src/{telemetry,renderer/src/telemetry}/**/*.{ts,tsx,js,jsx} : Configure OTLP HTTP export to Grafana Cloud; read endpoints/keys from env with main-safe prefixes; do not expose secrets to renderer

Applied to files:

  • src/main/index.js
📚 Learning: 2025-09-18T22:47:22.731Z
Learnt from: CR
PR: BP602/KickTalk#0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T22:47:22.731Z
Learning: Applies to src/{telemetry,renderer/src/telemetry}/**/*.{ts,tsx,js,jsx} : Place OpenTelemetry instrumentation under src/telemetry/** and src/renderer/src/telemetry/**

Applied to files:

  • src/main/index.js
🧬 Code graph analysis (3)
src/renderer/src/telemetry/webTracing.js (2)
src/main/index.js (4)
  • level (150-152)
  • getTelemetryDebug (137-145)
  • shouldLogDebug (159-161)
  • getTelemetryLevel (147-157)
src/telemetry/user-analytics.js (4)
  • level (20-22)
  • getTelemetryDebug (6-15)
  • shouldLogDebug (29-31)
  • getTelemetryLevel (17-27)
src/telemetry/user-analytics.js (3)
src/main/index.js (4)
  • getTelemetryDebug (137-145)
  • getTelemetryLevel (147-157)
  • level (150-152)
  • shouldLogDebug (159-161)
src/renderer/src/telemetry/webTracing.js (4)
  • getTelemetryDebug (211-219)
  • getTelemetryLevel (199-209)
  • level (202-204)
  • shouldLogDebug (221-223)
src/telemetry/tracing.js (1)
  • level (86-86)
src/main/index.js (2)
src/renderer/src/telemetry/webTracing.js (4)
  • getTelemetryDebug (211-219)
  • getTelemetryLevel (199-209)
  • level (202-204)
  • shouldLogDebug (221-223)
src/telemetry/user-analytics.js (4)
  • getTelemetryDebug (6-15)
  • getTelemetryLevel (17-27)
  • level (20-22)
  • shouldLogDebug (29-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test-builds (windows-latest)
🔇 Additional comments (9)
src/telemetry/user-analytics.js (7)

310-313: Good: endSession log gated by telemetry.

No action needed.


326-329: Good: startSession log gated by telemetry.


354-356: Good: satisfaction log gated by telemetry.


454-456: Good: feature usage log gated by telemetry.


492-494: Good: connection quality log gated by telemetry.


676-686: Good: force-cleanup log gated by telemetry.


5-31: Env-driven debug gating OK — confirm this file isn't imported by renderer

Reads process.env; ensure module src/telemetry/user-analytics.js is never bundled into or imported by any renderer code. Confirm with:
rg -n -C2 -g 'src/renderer/**' -e 'telemetry[/\]user-analytics' -e 'user-analytics'

src/main/index.js (2)

136-162: Main debug helpers align with renderer; good.


136-162: Search and fix legacy MAIN_VITELEMETRY typos

Earlier code used MAIN_VITELEMETRY (missing underscore). Search for MAIN_VITELEMETRY, MAINVITELEMETRY, VITELEMETRY (and renderer variants) and normalize them to VITE_TELEMETRY / MAIN_VITE_TELEMETRY to avoid split behavior. Automated sandbox search returned "No files were searched" — verification couldn't be completed; verify locally.

- Remove unnecessary refresh7TVEmotes() call from handleEmoteSetUpdate
- WebSocket events already provide updated emote data, API call was redundant
- Fixes excessive "Refreshing 7TV emotes..." messages in chat
- Add entitlement.delete event handling for proper cosmetic removal
- Restore proper Zustand subscription in Message component for live cosmetic updates
- Add deduplication for global cosmetic events (cosmetic.create, entitlement.*)
- Fix new chatroom 7TV subscription via connectionManager.addChatroom
- Add initial cosmetics loading when connecting to chatrooms
- Improve diagnostic logging throughout cosmetics flow
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
utils/services/connectionManager.js (1)

603-609: Inconsistent Kick ID passed to 7TV addChatroom (user_id vs id).

addChatroom uses streamerData.user_id; here syncSharedStvChatroom passes streamerData.id. Use user_id consistently or subscriptions may target the wrong subject.

   this.stvWebSocket.addChatroom(
     chatroom.id,
-    chatroom.streamerData?.id,
+    chatroom.streamerData?.user_id,
     stvId,
     stvEmoteSetId,
     chatroom // Pass the full chatroom data for ID variants
   );
utils/services/seventv/stvWebsocket.js (3)

51-71: Guard optional fields in paint processing.

data.stops and data.shadows may be absent; current code can throw on .length.

-      if (data.stops.length) {
+      if (Array.isArray(data.stops) && data.stops.length) {
         // ...
       } else {
         // ...
       }
-      if (data.shadows.length) {
+      if (Array.isArray(data.shadows) && data.shadows.length) {
         const shadows = data.shadows;
         // ...
       }

Also applies to: 101-115


108-110: Invalid CSS color in drop-shadow (rgba with 3 args).

Same issue as in stvAPI: use rgb(...) or keep alpha.

-            rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/, `rgba($1, $2, $3)`);
+            rgbaColor = rgbaColor.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[^)]+\)/, `rgb($1, $2, $3)`);

545-554: Fix ARGB→RGBA channel order and alpha handling.

Same bug here as stvAPI; colors will be wrong.

-const argbToRgba = (color) => {
+const argbToRgba = (color) => {
   if (color < 0) {
     color = color >>> 0;
   }
-
-  const red = (color >> 24) & 0xff;
-  const green = (color >> 16) & 0xff;
-  const blue = (color >> 8) & 0xff;
-  return `rgba(${red}, ${green}, ${blue}, 1)`;
+  const a = (color >>> 24) & 0xff;
+  const r = (color >>> 16) & 0xff;
+  const g = (color >>> 8) & 0xff;
+  const b = (color >>> 0) & 0xff;
+  const alpha = (a / 255).toFixed(3);
+  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
 };
utils/services/seventv/sharedStvWebSocket.js (5)

436-453: Fix exponential backoff: attempts never increment here → negative exponent, 500ms tight reconnect loop

Increment attempts in handleReconnection and ensure step ≥ 1. Also fix telemetry/logged attempt numbers.

   handleReconnection() {
-    if (!this.shouldReconnect) {
+    if (!this.shouldReconnect) {
       console.log(`[Shared7TV]: Reconnection disabled`);
       try {
         window.app?.telemetry?.recordWebSocketEvent?.('7tv_shared', 'reconnection_disabled', {
-          attempt: this.reconnectAttempts
+          attempt: this.reconnectAttempts
         });
       } catch (_) {}
       return;
     }
 
-    // exponential backoff: start * 2^(step-1)
-    // cap at maxRetrySteps, so after step 5 it stays at start * 2^(maxRetrySteps-1)
-    const step = Math.min(this.reconnectAttempts, this.maxRetrySteps);
-    const delay = this.startDelay * Math.pow(2, step - 1);
+    // exponential backoff: start * 2^(step-1); step starts at 1
+    this.reconnectAttempts++;
+    const step = Math.min(this.reconnectAttempts, this.maxRetrySteps);
+    const delay = this.startDelay * Math.pow(2, Math.max(1, step) - 1);
 
-    console.log(`[Shared7TV]: Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1})`);
+    console.log(`[Shared7TV]: Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
     try {
       window.app?.telemetry?.recordWebSocketEvent?.('7tv_shared', 'reconnection_scheduled', {
-        attempt: this.reconnectAttempts + 1,
+        attempt: this.reconnectAttempts,
         delay_ms: delay,
         step: step
       });
     } catch (_) {}
 
     setTimeout(() => {
       this.connect();
     }, delay);
   }

510-541: Subscribe user. for all unique STV user IDs, not just the first one*

Current code subscribes once and misses user events for additional chatrooms. Iterate unique stvIds and drop the boolean gate.

   async subscribeToUserEvents() {
-    if (this.userEventSubscribed || !this.chat || this.chat.readyState !== WebSocket.OPEN) {
+    if (!this.chat || this.chat.readyState !== WebSocket.OPEN) {
       return;
     }
 
-    // Find any chatroom with a valid 7TV user ID
-    const chatroomWithStvId = Array.from(this.chatrooms.values()).find(data => 
-      data.stvId && data.stvId !== "0" && data.stvId !== INVALID_7TV_NULL_ID
-    );
-    if (!chatroomWithStvId) {
+    const ids = Array.from(this.chatrooms.values())
+      .map(d => d.stvId)
+      .filter(id => id && id !== "0" && id !== INVALID_7TV_NULL_ID);
+    const uniqueIds = [...new Set(ids)];
+    if (uniqueIds.length === 0) {
       console.log(`[Shared7TV]: No valid 7TV user ID found for user events (skipping subscription)`);
       return;
     }
 
-    const eventKey = `user.*:${chatroomWithStvId.stvId}`;
-    if (this.subscribedEvents.has(eventKey)) {
-      return;
-    }
-
-    const subscribeUserMessage = {
-      op: 35,
-      d: {
-        type: "user.*",
-        condition: { object_id: chatroomWithStvId.stvId },
-      },
-    };
-
-    this.chat.send(JSON.stringify(subscribeUserMessage));
-    this.subscribedEvents.add(eventKey);
-    this.userEventSubscribed = true;
-    console.log(`[Shared7TV]: Subscribed to user.* events with stvId: ${chatroomWithStvId.stvId}`);
+    for (const stvId of uniqueIds) {
+      const eventKey = `user.*:${stvId}`;
+      if (this.subscribedEvents.has(eventKey)) continue;
+      const subscribeUserMessage = {
+        op: 35,
+        d: { type: "user.*", condition: { object_id: stvId } }
+      };
+      this.chat.send(JSON.stringify(subscribeUserMessage));
+      this.subscribedEvents.add(eventKey);
+      console.log(`[Shared7TV]: Subscribed to user.* events with stvId: ${stvId}`);
+    }
+    this.userEventSubscribed = this.subscribedEvents.size > 0;
   }

12-12: Make updateCosmetics synchronous and null-safe (stops/shadows may be undefined; unnecessary await causes race)

Avoid async here and remove the await on a synchronous join; guard .stops/.shadows to prevent runtime errors.

-const updateCosmetics = async (body) => {
+const updateCosmetics = (body) => {
@@
-      if (data.stops.length) {
+      if (Array.isArray(data?.stops) && data.stops.length) {
@@
-      if (data.shadows.length) {
-        const shadows = data.shadows;
-
-        shadow = await shadows
-          .map((shadow) => {
-            let rgbaColor = argbToRgba(shadow.color);
-
-            rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/, `rgba($1, $2, $3)`);
-
-            return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;
-          })
-          .join(" ");
-
-        push["shadows"] = shadow;
-      }
+      if (Array.isArray(data?.shadows) && data.shadows.length) {
+        const shadows = data.shadows;
+        const shadowCss = shadows
+          .map((s) => {
+            const rgba = argbToRgba(s.color);
+            const rgb = rgba.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[^)]+\)/, "rgb($1, $2, $3)");
+            return `drop-shadow(${rgb} ${s.x_offset}px ${s.y_offset}px ${s.radius}px)`;
+          })
+          .join(" ");
+        push["shadows"] = shadowCss;
+      }

Also applies to: 55-61, 105-119


938-947: ARGB → RGBA conversion is incorrect (channels shifted wrong); use alpha from ARGB

Current code maps A→R, R→G, G→B, ignores B. Fix shifts and alpha normalization.

 const argbToRgba = (color) => {
   if (color < 0) {
     color = color >>> 0;
   }
 
-  const red = (color >> 24) & 0xff;
-  const green = (color >> 16) & 0xff;
-  const blue = (color >> 8) & 0xff;
-  return `rgba(${red}, ${green}, ${blue}, 1)`;
+  const a = (color >> 24) & 0xff;       // AA
+  const r = (color >> 16) & 0xff;       // RR
+  const g = (color >> 8) & 0xff;        // GG
+  const b = color & 0xff;               // BB
+  const alpha = +(a / 255).toFixed(3);
+  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
 };

465-498: Channel-scoped cosmetic/entitlement subscriptions likely miss user-scoped events

7TV emits cosmetics/entitlements tied to users; channel ctx often won’t deliver expected events. Add user-scoped subscriptions (by stvId) alongside channel ones.

Example addition (outside this hunk):

async subscribeToUserScopedCosmeticAndEntitlementEvents(stvId) {
  if (!this.chat || this.chat.readyState !== WebSocket.OPEN) return;
  for (const type of ["cosmetic.*", "entitlement.*"]) {
    const key = `${type}:user:${stvId}`;
    if (this.subscribedEvents.has(key)) continue;
    this.chat.send(JSON.stringify({ op: 35, d: { type, condition: { object_id: stvId } } }));
    this.subscribedEvents.add(key);
    console.log(`[Shared7TV]: Subscribed to ${type} for stvUser ${stvId}`);
  }
}

Then call it from subscribeToChatroomEvents when stvId is valid:

const hasValidStvId = chatroomData.stvId && chatroomData.stvId !== "0" && chatroomData.stvId !== INVALID_7TV_NULL_ID;
if (hasValidStvId) await this.subscribeToUserScopedCosmeticAndEntitlementEvents(chatroomData.stvId);
🧹 Nitpick comments (14)
src/renderer/src/components/Messages/ModActionMessage.jsx (1)

15-44: Missing dependency in useCallback.

userChatroomInfo is referenced but not in deps; could capture stale value.

-  );
+  );
   // ...
-    [chatroomName, username, chatroomId, allStvEmotes, subscriberBadges],
+    [chatroomName, username, chatroomId, allStvEmotes, subscriberBadges, userChatroomInfo],
src/renderer/src/components/Messages/Message.jsx (1)

44-55: Avoid high-frequency getUserStyle calls (noise + cost).

This selector calls a noisy getter every render; it warns if called <100ms apart. Derive from state directly to avoid logs, or gate logs behind a debug flag.

-  const subscribedUserStyle = useCosmeticsStore(
-    useShallow((state) => {
-      if (!message?.sender || type === "replyThread" || type === "dialog") {
-        return null;
-      }
-      return state.getUserStyle(message.sender.username);
-    }),
-  );
+  const subscribedUserStyle = useCosmeticsStore(useShallow((state) => {
+    if (!message?.sender || type === "replyThread" || type === "dialog") return null;
+    const uname = message.sender.username?.toLowerCase()?.replaceAll('-', '_');
+    const us = state.userStyles[uname];
+    if (!us?.badgeId && !us?.paintId) return null;
+    const badge = state.globalCosmetics.badges?.find((b) => b.id === us.badgeId);
+    const paint = state.globalCosmetics.paints?.find((p) => p.id === us.paintId);
+    return { badge, paint, color: us.color, username: us.username };
+  }));
utils/services/seventv/stvAPI.js (2)

254-356: Function name and unused param.

This fetches a global catalog and ignores channelId. Either rename (e.g., getCosmeticsCatalog) or document and remove the param.

Would you prefer we rename the function and update preload/ChatProvider accordingly?


260-265: Add request timeout and basic retry.

Network call should have a timeout to avoid hanging.

-    const response = await axios.get(`https://api.7tv.app/v2/cosmetics`);
+    const response = await axios.get(`https://api.7tv.app/v2/cosmetics`, { timeout: 10000 });
src/renderer/src/providers/CosmeticsProvider.jsx (2)

61-63: Connection discriminator mismatch ('type' vs 'platform').

Elsewhere you check c.platform === "KICK". Use the same key here.

-            kickConnection: userStyle.connections?.find((c) => c.type === "KICK"),
+            kickConnection: userStyle.connections?.find((c) => c.platform === "KICK"),

11-21: Gate verbose diagnostics behind a debug flag.

Logs are very chatty and executed in hot paths; wrap with a feature flag (e.g., window.KT_TELEMETRY_UTILS?.shouldLogDebug?.()).

Also applies to: 22-31, 35-45, 51-53, 170-179, 188-196, 205-213, 215-220, 228-234, 257-279, 305-308

src/renderer/src/providers/ChatProvider.jsx (1)

555-557: Gate debug logs.

Use your telemetry debug flag for these presence logs to keep consoles clean.

-      if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) {
+      if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) {
         console.log("[7tv Presence]: Last update time for chatroom:", userId, lastUpdateTime, stvPresenceUpdates);
       }
-          if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) {
+          if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) {
             console.log("[7tv Presence]: Sending presence check for chatroom:", chatroom.streamerData.user_id);
           }

Also applies to: 4131-4133

utils/services/seventv/sharedStvWebSocket.js (7)

213-218: Also (re)subscribe user. when adding a chatroom on an already-open socket*

Without this, user events for newly added chatrooms won't be subscribed.

   if (this.connectionState === 'connected') {
     console.log(`[Shared7TV DIAGNOSTIC]: Subscribing to events for new chatroom ${chatroomId}`);
     this.subscribeToChatroomEvents(chatroomId);
+    // ensure user-scoped subscriptions are up to date
+    this.subscribeToUserEvents();
   } else {

31-37: Badge URL construction lacks host/files guards; can throw if host/files missing

Make it resilient to missing host or empty files array.

-      const newBadge = {
-        id: data.id === "00000000000000000000000000" ? data.ref_id || "default_id" : data.id,
-        title: data.tooltip,
-        url: `https:${data.host.url}/${data.host.files[data.host.files.length - 1].name}`,
-      };
+      const id = data.id === "00000000000000000000000000" ? (data.ref_id || "default_id") : data.id;
+      const baseUrl = typeof data?.host?.url === "string"
+        ? (data.host.url.startsWith("//") ? `https:${data.host.url}` : data.host.url)
+        : null;
+      const lastFile = Array.isArray(data?.host?.files) && data.host.files.length
+        ? data.host.files[data.host.files.length - 1].name
+        : null;
+      const newBadge = {
+        id,
+        title: data.tooltip,
+        url: baseUrl && lastFile ? `${baseUrl}/${lastFile}` : null,
+      };

824-837: Check entitlement body shape before filtering by kind

Some 7TV entitlement events carry kind under body.object.kind; guard both to avoid dropping valid events.

-        // Process entitlements for both cosmetics (kind 10) and emote sets (kind 5)
-        if (body.kind === 10 || body.kind === 5) {
+        // Process entitlements for both cosmetics (kind 10) and emote sets (kind 5)
+        const kind = body?.kind ?? body?.object?.kind;
+        if (kind === 10 || kind === 5) {

381-395: Gate verbose console logs behind a debug flag to reduce prod noise

Keep diagnostics, but guard with a single isDebug flag or telemetry-only in production.

- console.log(`[Shared7TV]: ...`);
+ if (window.__DEBUG_FLAGS__?.stv) console.log(`[Shared7TV]: ...`);

Also applies to: 397-400, 465-498, 542-569, 571-598, 633-757, 121-123, 37-37, 215-218


767-771: Avoid logging PII (usernames) unless gated

Usernames can be considered PII; ensure debug-only.

Also applies to: 775-782


173-204: Store STV user ID variant for routing (consistency with idVariants block)

Add stvId into idVariants to improve mapping.

   if (chatroomData) {
     storedData.idVariants = {
       chatroom_id: String(chatroomId),
       streamer_id: String(channelKickID),
       streamer_user_id: chatroomData.streamerData?.user_id ? String(chatroomData.streamerData.user_id) : null,
       username: chatroomData.streamerData?.username || null,
       user_username: chatroomData.streamerData?.user?.username || null,
       slug: chatroomData.slug || null,
+      stv_user_id: storedData.stvId || null,
     };

865-872: Emote set routing: body.id might be nested; add fallback to body.object.id/object_id

Prevents misrouting when the event schema differs.

-    if (type.startsWith("emote_set.") && body?.id) {
+    if (type.startsWith("emote_set.") && (body?.id || body?.object_id || body?.object?.id)) {
       for (const [chatroomId, data] of this.chatrooms) {
-        if (data.stvEmoteSetId === body.id) {
+        const incomingId = body?.id ?? body?.object_id ?? body?.object?.id;
+        if (data.stvEmoteSetId === incomingId) {
           return chatroomId;
         }
       }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a63ec74 and a73392c.

📒 Files selected for processing (9)
  • src/preload/index.js (2 hunks)
  • src/renderer/src/components/Messages/Message.jsx (2 hunks)
  • src/renderer/src/components/Messages/ModActionMessage.jsx (1 hunks)
  • src/renderer/src/providers/ChatProvider.jsx (7 hunks)
  • src/renderer/src/providers/CosmeticsProvider.jsx (4 hunks)
  • utils/services/connectionManager.js (4 hunks)
  • utils/services/seventv/sharedStvWebSocket.js (14 hunks)
  • utils/services/seventv/stvAPI.js (1 hunks)
  • utils/services/seventv/stvWebsocket.js (4 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**

📄 CodeRabbit inference engine (AGENTS.md)

**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes

Files:

  • utils/services/seventv/stvAPI.js
  • src/preload/index.js
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/components/Messages/Message.jsx
  • src/renderer/src/components/Messages/ModActionMessage.jsx
  • utils/services/seventv/sharedStvWebSocket.js
  • utils/services/seventv/stvWebsocket.js
  • src/renderer/src/providers/ChatProvider.jsx
  • utils/services/connectionManager.js
!dist/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not commit built installers/artifacts in dist/

Files:

  • utils/services/seventv/stvAPI.js
  • src/preload/index.js
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/components/Messages/Message.jsx
  • src/renderer/src/components/Messages/ModActionMessage.jsx
  • utils/services/seventv/sharedStvWebSocket.js
  • utils/services/seventv/stvWebsocket.js
  • src/renderer/src/providers/ChatProvider.jsx
  • utils/services/connectionManager.js
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))

Files:

  • src/preload/index.js
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/components/Messages/Message.jsx
  • src/renderer/src/components/Messages/ModActionMessage.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/preload/**/*.{ts,js,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

src/preload/**/*.{ts,js,mts,mjs,cjs}: Expose a minimal, purpose-built API from preload via contextBridge.exposeInMainWorld
Do not expose raw Node objects/APIs from preload
Use import.meta.env.PRELOAD_VITE_* for preload-scoped config

Files:

  • src/preload/index.js
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use shared non-secret variables via import.meta.env.VITE_*

Files:

  • src/preload/index.js
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/components/Messages/Message.jsx
  • src/renderer/src/components/Messages/ModActionMessage.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/renderer/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead

Files:

  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/components/Messages/Message.jsx
  • src/renderer/src/components/Messages/ModActionMessage.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'

Files:

  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/components/Messages/Message.jsx
  • src/renderer/src/components/Messages/ModActionMessage.jsx
  • src/renderer/src/providers/ChatProvider.jsx
🧠 Learnings (1)
📚 Learning: 2025-09-18T22:47:22.731Z
Learnt from: CR
PR: BP602/KickTalk#0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T22:47:22.731Z
Learning: Applies to src/preload/**/*.{ts,js,mts,mjs,cjs} : Expose a minimal, purpose-built API from preload via contextBridge.exposeInMainWorld

Applied to files:

  • src/preload/index.js
🧬 Code graph analysis (7)
utils/services/seventv/stvAPI.js (2)
utils/services/seventv/sharedStvWebSocket.js (10)
  • cosmetics (4-7)
  • randomColor (51-51)
  • normalizedColors (56-59)
  • gradient (61-61)
  • argbToRgba (938-947)
  • isDeg_or_Shape (69-69)
  • shadow (103-103)
  • red (943-943)
  • green (944-944)
  • blue (945-945)
utils/services/seventv/stvWebsocket.js (10)
  • cosmetics (3-6)
  • randomColor (47-47)
  • normalizedColors (52-55)
  • gradient (57-57)
  • argbToRgba (545-554)
  • isDeg_or_Shape (65-65)
  • shadow (99-99)
  • red (550-550)
  • green (551-551)
  • blue (552-552)
src/renderer/src/providers/CosmeticsProvider.jsx (2)
utils/services/seventv/sharedStvWebSocket.js (1)
  • body (17-17)
utils/services/seventv/stvWebsocket.js (1)
  • body (13-13)
src/renderer/src/components/Messages/Message.jsx (2)
src/renderer/src/providers/CosmeticsProvider.jsx (1)
  • useCosmeticsStore (3-309)
src/renderer/src/components/Dialogs/User.jsx (1)
  • dialogUserStyle (21-21)
src/renderer/src/components/Messages/ModActionMessage.jsx (2)
src/renderer/src/components/Messages/Message.jsx (1)
  • userStyle (54-54)
src/renderer/src/providers/CosmeticsProvider.jsx (1)
  • useCosmeticsStore (3-309)
utils/services/seventv/sharedStvWebSocket.js (2)
utils/services/seventv/stvAPI.js (1)
  • cosmetics (268-268)
utils/services/seventv/stvWebsocket.js (3)
  • cosmetics (3-6)
  • push (49-49)
  • body (13-13)
src/renderer/src/providers/ChatProvider.jsx (1)
src/renderer/src/providers/CosmeticsProvider.jsx (1)
  • useCosmeticsStore (3-309)
utils/services/connectionManager.js (2)
src/renderer/src/components/Chat/index.jsx (1)
  • chatroom (17-17)
src/renderer/src/components/Chat/Input/index.jsx (1)
  • chatroom (1166-1166)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test-builds (windows-latest)
  • GitHub Check: test-builds (macos-latest)
🔇 Additional comments (10)
src/renderer/src/components/Messages/Message.jsx (1)

7-7: LGTM: correct import path for useShallow.

src/preload/index.js (1)

38-38: LGTM: minimal, purpose-built preload API exposure (stv.getChannelCosmetics).

Matches the preload guideline; no raw Node/Electron objects exposed.

Also applies to: 498-502

utils/services/connectionManager.js (1)

121-127: LGTM: explicit handler registration logs help traceability.

src/renderer/src/providers/ChatProvider.jsx (2)

1230-1246: LGTM: initial cosmetics load via preload STV API.

Good guarding and store update.


1961-1975: Duplicate normalization logic diverges from store.

Here you normalize with '-'→'_' + toLowerCase(); align with CosmeticsProvider (or centralize a helper) to avoid future drift.

Option A: keep normalization here and update CosmeticsProvider (see related comment).
Option B: create utils/normalizeUsername and use it in both places.

Also applies to: 1981-1997

src/renderer/src/components/Messages/ModActionMessage.jsx (1)

21-21: Normalize username before cosmetics lookup (hyphenated names break).

ChatProvider writes styles under username.replaceAll('-', '_').toLowerCase(), but getUserStyle here uses the raw username. Hyphenated users won’t resolve styles. Either centralize a normalizer or normalize here.

-      const userStyle = useCosmeticsStore.getState().getUserStyle(usernameDialog);
+      const normalized = usernameDialog?.replaceAll('-', '_').toLowerCase();
+      const userStyle = useCosmeticsStore.getState().getUserStyle(normalized);
src/renderer/src/providers/CosmeticsProvider.jsx (1)

32-34: Unify username normalization (reader vs writer).

Store uses toLowerCase(); ChatProvider writes with '-'→'_' + toLowerCase(). Normalize identically here to avoid cache misses.

-    const transformedUsername = username.toLowerCase();
+    const transformedUsername = username?.toLowerCase()?.replaceAll('-', '_');
-    const transformedUsername = username.toLowerCase();
+    const transformedUsername = username?.toLowerCase()?.replaceAll('-', '_');

Also applies to: 149-156

utils/services/seventv/stvAPI.js (1)

270-279: No change required — v2 badges use urls array

Verified: the legacy /v2/cosmetics response returns badges[].urls (e.g., ["1","https://cdn.7tv.app/.../1x", ""]), so the mapping in utils/services/seventv/stvAPI.js (lines 270–279) is correct; if you migrate to newer Event/API endpoints they may use host/files.

utils/services/seventv/sharedStvWebSocket.js (2)

810-822: If updateCosmetics stays async, await it before dispatching; else keep it sync as suggested above

Ensures shadows/styles are fully computed before broadcasting.

-          case "cosmetic.create":
-            updateCosmetics(body);
+          case "cosmetic.create":
+            // If updateCosmetics remains async, await it:
+            await updateCosmetics(body);

If you adopt the synchronous refactor above, ignore this change.


201-212: No action required — addChatroom call sites are compatible with the new signature

Repo search shows sharedStvWebSocket.addChatroom is only invoked from utils/services/connectionManager.js (calls at lines ~319 and ~603); sharedStvWebSocket.addChatroom declares defaults for stvId, stvEmoteSetId, and chatroomData, so existing calls are compatible — no arity mismatches found.

Comment on lines +311 to +329
// Expose test function globally for debugging
if (typeof window !== 'undefined') {
window.testCosmeticsStore = () => {
console.log(`[TEST] Starting cosmetics test...`);
const store = useCosmeticsStore.getState();
console.log(`[TEST] Got store:`, !!store);
store.testCosmetics();

// Also test retrieval
setTimeout(() => {
const result = store.getUserStyle('testuser');
console.log(`[CosmeticsStore TEST] getUserStyle result:`, result);
}, 100);
};

// Also expose the store itself for direct testing
window.cosmeticsStore = useCosmeticsStore;
console.log(`[CosmeticsProvider] Exposed window.testCosmeticsStore() and window.cosmeticsStore`);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t expose debug hooks in production.

window.testCosmeticsStore and window.cosmeticsStore should be dev-only.

-if (typeof window !== 'undefined') {
+if (typeof window !== 'undefined' && import.meta.env.DEV) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Expose test function globally for debugging
if (typeof window !== 'undefined') {
window.testCosmeticsStore = () => {
console.log(`[TEST] Starting cosmetics test...`);
const store = useCosmeticsStore.getState();
console.log(`[TEST] Got store:`, !!store);
store.testCosmetics();
// Also test retrieval
setTimeout(() => {
const result = store.getUserStyle('testuser');
console.log(`[CosmeticsStore TEST] getUserStyle result:`, result);
}, 100);
};
// Also expose the store itself for direct testing
window.cosmeticsStore = useCosmeticsStore;
console.log(`[CosmeticsProvider] Exposed window.testCosmeticsStore() and window.cosmeticsStore`);
}
// Expose test function globally for debugging
if (typeof window !== 'undefined' && import.meta.env.DEV) {
window.testCosmeticsStore = () => {
console.log(`[TEST] Starting cosmetics test...`);
const store = useCosmeticsStore.getState();
console.log(`[TEST] Got store:`, !!store);
store.testCosmetics();
// Also test retrieval
setTimeout(() => {
const result = store.getUserStyle('testuser');
console.log(`[CosmeticsStore TEST] getUserStyle result:`, result);
}, 100);
};
// Also expose the store itself for direct testing
window.cosmeticsStore = useCosmeticsStore;
console.log(`[CosmeticsProvider] Exposed window.testCosmeticsStore() and window.cosmeticsStore`);
}
🤖 Prompt for AI Agents
In src/renderer/src/providers/CosmeticsProvider.jsx around lines 311 to 329, the
debug helpers are being attached to window unconditionally; restrict these to
non-production builds by wrapping the exposure in a development-only check (e.g.
process.env.NODE_ENV !== 'production' or your app's equivalent like
import.meta.env.DEV), only define window.testCosmeticsStore and
window.cosmeticsStore inside that guard, and remove or reduce console noise if
desired; ensure bundlers/tree-shakers will drop this block in production by
using the canonical env variable your build uses.

Comment on lines +331 to +339
const shadow = paint.data.shadows
.map((shadow) => {
let rgbaColor = argbToRgba(shadow.color);
rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/, `rgba($1, $2, $3)`);
return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;
})
.join(" ");
paintObject.shadows = shadow;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Invalid CSS color in drop-shadow (rgba with 3 args).

The replace produces rgba(r,g,b) which is invalid; use rgb(...) or keep alpha.

-              let rgbaColor = argbToRgba(shadow.color);
-              rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/, `rgba($1, $2, $3)`);
-              return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;
+              const rgbaColor = argbToRgba(shadow.color);
+              const rgbColor = rgbaColor.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[^)]+\)/, `rgb($1, $2, $3)`);
+              return `drop-shadow(${rgbColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const shadow = paint.data.shadows
.map((shadow) => {
let rgbaColor = argbToRgba(shadow.color);
rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/, `rgba($1, $2, $3)`);
return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;
})
.join(" ");
paintObject.shadows = shadow;
}
const shadow = paint.data.shadows
.map((shadow) => {
const rgbaColor = argbToRgba(shadow.color);
const rgbColor = rgbaColor.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[^)]+\)/, `rgb($1, $2, $3)`);
return `drop-shadow(${rgbColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;
})
.join(" ");
paintObject.shadows = shadow;
🤖 Prompt for AI Agents
In utils/services/seventv/stvAPI.js around lines 331 to 339, the current replace
turns rgba(...) with 4 args into an invalid rgba(r,g,b) 3-arg string; change the
logic to parse the rgba capture groups and output either rgb(r,g,b) when alpha
is 1 (or absent) or a proper rgba(r,g,b,a) when alpha < 1, i.e. extract the
captured r,g,b,a from the regex and conditionally format the color string
accordingly before building the drop-shadow so the resulting CSS color is valid.

Comment on lines +359 to +368
const argbToRgba = (color) => {
if (color < 0) {
color = color >>> 0;
}

const red = (color >> 24) & 0xff;
const green = (color >> 16) & 0xff;
const blue = (color >> 8) & 0xff;
return `rgba(${red}, ${green}, ${blue}, 1)`;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

ARGB→RGBA channel order is wrong and alpha is dropped.

Current shifts yield A,R,G as R,G,B and hardcode alpha=1. Fix bit extraction and preserve alpha.

-const argbToRgba = (color) => {
+const argbToRgba = (color) => {
   if (color < 0) {
     color = color >>> 0;
   }
-
-  const red = (color >> 24) & 0xff;
-  const green = (color >> 16) & 0xff;
-  const blue = (color >> 8) & 0xff;
-  return `rgba(${red}, ${green}, ${blue}, 1)`;
+  const a = (color >>> 24) & 0xff;
+  const r = (color >>> 16) & 0xff;
+  const g = (color >>> 8) & 0xff;
+  const b = (color >>> 0) & 0xff;
+  const alpha = (a / 255).toFixed(3);
+  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const argbToRgba = (color) => {
if (color < 0) {
color = color >>> 0;
}
const red = (color >> 24) & 0xff;
const green = (color >> 16) & 0xff;
const blue = (color >> 8) & 0xff;
return `rgba(${red}, ${green}, ${blue}, 1)`;
};
const argbToRgba = (color) => {
if (color < 0) {
color = color >>> 0;
}
const a = (color >>> 24) & 0xff;
const r = (color >>> 16) & 0xff;
const g = (color >>> 8) & 0xff;
const b = (color >>> 0) & 0xff;
const alpha = (a / 255).toFixed(3);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
🤖 Prompt for AI Agents
In utils/services/seventv/stvAPI.js around lines 359 to 368, the ARGB→RGBA
conversion currently extracts the wrong channels and forces alpha to 1; update
the bit extraction so alpha = (color >>> 24) & 0xff, red = (color >>> 16) &
0xff, green = (color >>> 8) & 0xff, blue = color & 0xff, and return the string
with the preserved alpha as a normalized fraction (alpha / 255) in the rgba(...)
result; keep the initial unsigned conversion (color = color >>> 0) and use
unsigned right shifts (>>>) for all extractions.

@BP602 BP602 closed this Oct 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant